
#include <memory.h>
#include <ctype.h>

#define DUA_CORE

#include "dua_def.h"

static inline int
isNeg(const char** s) {
    if (**s == '-') {
        (*s)++;
        return 1;
    }
    else if (**s == '+') {
        (*s)++;
    }
    return 0;
}

static inline int
hexValue(int c) {
    if (isdigit(c)) {
        return c - '0';
    }
    else {
        return (tolower(c) - 'a') + 10;
    }
}

static const char*
str2int(const char* s, int64_t* r) {
    int64_t a = 0;
    int empty = 1;
    int neg;
    while (isspace(*s)) { s++; }
    neg = isNeg(&s);
    if (s[0] == '0' &&
        (s[1] == 'x' || s[1] == 'X')) {
        s += 2;
        for (; isxdigit(*s); s++) {
            a = a * 16 + hexValue(*s);
            empty = 0;
        }
    }
    else {
        for (; isdigit(*s); s++) {
            int d = *s - '0';
            a = a * 10 + (*s - '0');
            empty = 0;
        }
    }
    while (isspace(*s)) { s++; }
    if (empty || (*s != '\0')) {
        return NULL;
    }
    else {
        *r = neg ? (0ll - a) : a;
        return s;
    }
}

static const char*
str2flt(const char* s, double* d) {
    char* endptr;
    *d = strtod(s, &endptr);
    if (endptr == s) {
        return NULL;
    }
    while (isspace(*endptr)) { endptr++; }
    return (*endptr == '\0') ? endptr : NULL;
}


static uint32_t
hashStr(const char* s, size_t len){
    uint32_t hash = 5381;
    for (size_t i = 0; i < len; i++){
        hash = ((hash << 5) + hash) + s[i];
    }
    return hash;
}

static void
expandStrMap(DuaState* D){
    DuaStrMap* map = D->vm->strmap;
    unsigned cap = map->cap * 2;
    map->slot = duaRelloc(D, map->slot,
            sizeof(DuaString*) * map->cap,
            sizeof(DuaString*) * cap);
    map->cap = cap;
    for(unsigned i = map->count; i < cap; i ++){
        map->slot[i] = NULL;
    }
    DuaString * ts;
    for (unsigned i = 0; i < map->cap; i ++){
        ts = map->slot[i];
        map->slot[i] = NULL;
        while(ts){
            uint32_t h = (ts->hash % cap);
            ts->next = map->slot[h];
            map->slot[h] = ts;
            ts = ts->next;
        }
    }
}

static DuaString*
internalStr (DuaState* D, const char * str, size_t len){
    DuaStrMap * map = D->vm->strmap;
    uint32_t h = hashStr(str, len);
    DuaString ** list = &(map->slot[h % map->cap]);
    DuaString * ts;
    for (ts = *list; ts != NULL; ts = ts->next){
        if(len == ts->len && ts->hash == h &&
                (memcmp(str, ts->s, len) == 0)){
            return ts;
        }
    }
    if(map->count >= map->cap){
        expandStrMap(D);
        list = &(map->slot[h % map->cap]);
    }
    size_t tl = sizeof(DuaString) + len + 1;
    ts = duaNewObj(D, DUA_TSS, tl);
    ts->len = len;
    ts->hash = h;
    ts->next = *list;
    ts->flag = 0;
    ts->s = ((char*)ts) + sizeof(DuaString);
    *list = ts;
    map->count ++;
    memcpy(ts->s, str, len);
    ts->s[len] = 0;
    return ts;
}

extern int
duaEqualTs(DuaString* a, DuaString* b){
    return (a == b) ||
        ((a->len == b->len) &&
         (memcmp(a->s, b->s, a->len) == 0));
}

extern void
duaInitString(DuaState* D){
    DuaStrMap * map = duaNewObj(D, 0, sizeof(DuaStrMap));
    D->vm->strmap = map;
    unsigned cap = 256;
    map->slot = duaMalloc(D, sizeof(DuaString*) * cap);
    map->cap = cap;
    map->count = 0;
    memset(map->slot, 0, sizeof(DuaString*) * cap);
}

extern void
duaCloseString(DuaState* D){
    DuaStrMap * map = D->vm->strmap;
    duaFree(D, map->slot, sizeof(DuaString*) * map->cap);
}

extern DuaString*
duaNewStringLen(DuaState* D, const char* str, size_t len){
    if(len < DUA_SSTR_LEN){
        return internalStr(D, str, len);
    }else{
        size_t tl = sizeof(DuaString) + len + 1;
        DuaString * ts = duaNewObj(D, DUA_TLS, tl);
        ts->len = len;
        ts->flag = 0;
        ts->s = ((char*)ts) + sizeof(DuaString);
        memcpy(ts->s, str, len);
        ts->s[len] = '\0';
        return ts;
    }
}

extern char*
duaToNumber(const char* str, DuaValue* sem) {
    int64_t l; double d;
    const char* e = NULL;
    if ((e = str2int(str, &l)) != NULL) {
        sem->type = DUA_TLONG;
        sem->l = l;
    }
    else if ((e = str2flt(str, &d)) != NULL) {
        sem->type = DUA_TDOUBLE;
        sem->d = d;
    }
    return (char*)e;
}